package com.jt.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.util.Arrays;

//1.AOP需要被Spring容器管理
@Component
//2.标识该类为AOP切面
//  Spring容器默认不能识别切面注解,需要手动配置
//@Aspect
public class SpringAOP {

    //面向切面编程 = 切入点表达式(IF判断) + 通知方法


    /**
     * 切入点表达式练习
     * within:
     *  1.within(com.jt.*.DeptServiceImpl)   一级包下的类
     *  2.within(com.jt..*.DeptServiceImpl)  ..代表多级包下的类
     *  3.within(com.jt..*)  包下的所有的类
     *
     * execution(返回值类型 包名.类名.方法名(参数列表))
     *  1.execution(* com.jt..*.DeptServiceImpl.add*())
     *  注释: 返回值类型任意的, com.jt下的所有包中的DeptServiceImpl的类
     *        的add开头的方法 ,并且没有参数.
     *
     *  2.execution(* com.jt..*.*(..))
     *  注释: 返回值类型任意,com.jt包下的所有包的所有类的所有方法 任意参数.
     *
     *  3.execution(int com.jt..*.*(int))
     *  4.execution(Integer com.jt..*.*(Integer))
     *  强调: 在Spring表达式中没有自动拆装箱功能! 注意参数类型
     *
     * @annotation(包名.注解名)
     *     @Before("@annotation(com.jt.anno.Cache)")
     *    只拦截特定注解的内容.
     */


    //1.定义before通知
    //@Before("bean(deptServiceImpl)")
    //@Before("within(com.jt..*)")
    //@Before("execution(* com.jt..*.DeptServiceImpl.add*())")
    //@Before("@annotation(com.jt.anno.Cache)")

    //1.定义切入点表达式  if判断
    @Pointcut("@annotation(com.jt.anno.Cache)")
    public void pointcut(){

    }

    /*Spring为了AOP动态获取目标对象及方法中的数据,则通过joinPoint对象
    * 进行数据的传递.
    * getSignature : 方法签名  获取方法的参数
    * */
    @Before("pointcut()")
    public void before(JoinPoint joinPoint){
        System.out.println("获取目标对象的类型:"+joinPoint.getTarget().getClass());
        System.out.println("获取目标对象类名:"+joinPoint.getSignature().getDeclaringTypeName());
        System.out.println("获取目标对象方法名:"+joinPoint.getSignature().getName());
        System.out.println("获取方法参数:"+ Arrays.toString(joinPoint.getArgs()));
        System.out.println("我是before通知");
    }

    /**
     * 记录方法的方法返回值!!!
     * pointcut:  关联的切入点表达式
     * returning: 将方法的返回值,通过形参result进行传递
     * @AfterReturning(pointcut = "pointcut()",returning = "result")
     * 注意事项:
     *      如果参数中需要添加joinPoint 对象时,参数必须位于第一位.
     *      Spring在进行参数赋值时,采用index[0] 下标的方式赋值
     * 报错提示: ::0xxxx
     */
    @AfterReturning(pointcut = "pointcut()",returning = "result")
    public void afterReturning(JoinPoint joinPoint,Object result){
        System.out.println(Arrays.toString(joinPoint.getArgs()));
        System.out.println("用户的返回值结果:"+result);
        System.out.println("我是afterReturning通知");
    }

    /*
    * throwing = "e" 动态接收程序运行时的报错信息,
    * 利用异常通知进行记录
    * */
    @AfterThrowing(pointcut = "pointcut()",throwing = "e")
    public void afterThrowing(Exception e){
        System.out.println("获取异常信息:"+e.getMessage());
        System.out.println("获取异常的类型:"+e.getClass());
        System.out.println("我是afterThrowing");

    }

    @After("pointcut()")
    public void after(){
        System.out.println("我是after通知");
    }

    /**
     * 关于环绕通知的说明
     * 作用: 可以控制目标方法是否执行.
     * 参数: ProceedingJoinPoint 通过proceed方法控制目标方法执行.
     * 注意事项:
     *      ProceedingJoinPoint 只能适用环绕通知
     * @return
     */
    @Around("pointcut()")
    public Object around(ProceedingJoinPoint joinPoint){
        Object result = null;
        try {
            System.out.println("环绕通知开始");
            //1.执行下一个通知  2.执行目标方法 3.接收返回值
            Long start = System.currentTimeMillis();
            result = joinPoint.proceed();
            Long end = System.currentTimeMillis();
            System.out.println("耗时:"+(end-start));
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        System.out.println("环绕通知结束");
        return result;
    }
}
